{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 原计划将慢速收发数据\n",
    "- 日常真实的网络编程跟收发网络数据没什么关系, 关键是实现业务逻辑. 前面讲的和socket API打交道的ttcp, netcat, roundtrip反而是非典型网络编程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# What is procmon"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- A process monitoring tool (top) with network interface\n",
    "  - Good for monitoring long running process\n",
    "- Exposeprocess status with a customized HTTP server\n",
    "  - Universally accessible, no SSH\n",
    "- Non-intrusive monitoring by reading /proc/pid/\n",
    "  - man 5 proc\n",
    "  - Can be made more sophisticated with modern API\n",
    "- Code location: [muduo/examples/procmon](https://github.com/chenshuo/muduo/tree/master/examples/procmon)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 本讲实现一个进程监控工具, 可以看成带web界面的`top`. 这样就不需要登陆到远程主机去观察某个资源的进程消耗\n",
    "- 工具原理很简单, 读proc下面的文件, 做成html页面, 进行显示; 可以做到和ps, top这样的工具一样. \n",
    "  - proc文件系统CPU时间精确到1个tick(一般10ms), 如果每秒钟读一次CPU时间, 可以计算出CPU使用率, 比如这1秒钟比上一秒增加了30个tick, 那CPU使用率就是30%"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 观察`init`\n",
    "\n",
    "```shell\n",
    "./procmon l 3000\n",
    "```\n",
    "\n",
    "`10.0.0.37:3000`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 观察假负载[dummyload](https://github.com/chenshuo/muduo/blob/master/examples/procmon/dummyload.cc)\n",
    "\n",
    "```shell\n",
    "./dummyload c 80 2 # cpu负载生成器, 将CPU负载维持在指定的水平, 例如将CPU使用率绘制正弦曲线;\n",
    "```\n",
    "\n",
    "```shell\n",
    "./procmon 2755 2345\n",
    "```\n",
    "\n",
    "```shell\n",
    "top\n",
    "```\n",
    "\n",
    "`10.0.0.37:2345`\n",
    "\n",
    "> `shift + h`: 切换显示进程或线程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- top: 看到当前的CPU负载, 看不到历史\n",
    "- procmon: 不用root, 只能监控同一用户下的进程, top也一样"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Design decisions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Intrusive or non-intrusive?\n",
    "  - Internal data, eg. queries per second\n",
    "  - Run as same useror root? (/proc/pid/{cwd,exe,fd} are private)\n",
    "  - Implement once and monitor all processes in any language"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 侵入式, 还是非侵入式: 侵入式做成一个库, 被监控程序主动使用这个库, 去暴露其监控状态, 好处可以暴露其内容状态(收到多少次请求, 成功多少次, 失败多少次, 平均每个请求延迟多少毫秒), 要求库和引用语言一直; 非侵入式可以监控任何程序, 看不到程序内容状态, 通讯上用了多少内存, 多少CPU, CPU是用在了正常处理逻辑上, 还是死循环了区分不开. CPU和每秒钟处理的请求数应该是接近线性关系在一定区间内, 如果发现CPU正常, 但每秒请求数少了, 程序就是异常了.如果发现CPU正常, 但每秒请求数高了, 可能是程序BUG, 或者请求都是空的\n",
    "- 侵入式和被监控程序在同一用户下,权限一致"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- How to draw a graph?\n",
    "  - Standalone chart server (`![img](http://chart/?data=1,0.9,0.8,0.2)`)\n",
    "  - Render with JavaScript: jqPlot, flot\n",
    "  - Dynamically generate PNG image with libgd (`![img](/cpu.png)`)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 如何画图: \n",
    "  - 使用画图服务器, 生成PNG图片\n",
    "  - 使用json, 交给浏览器\n",
    "  - 返回PNG, 给浏览器"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# A customized stateful HTTP server"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Why not use a standard httpd and write a CGI script?\n",
    "  - If it simply read /proc/pid/ ?\n",
    "- CPU usage graph needs historical data\n",
    "- We are implementing HttpServlet.doGet(), basically"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- promom: 定制的HTTP服务器, 业务逻辑嵌入HTTP服务进程中, 代码与之前的网络程序有显著区别, 不去关注网络数据的收发(read, write), 只是对输入,请求产生特定的响应, **专注的是业务逻辑, 这才是网络编程的常态**, 大量经理学习业务领域的知识, 而不是使用socket API. \n",
    "- 既然是读proc文件, 为什么不用一个标准的http服务器处理这个http请求, 用Nginx或者lightweight http这种服务器?后用python实现一个标准的CGI, 这是一个标准的做法. 没有这样做的原因是, 绘制CPU历史曲线, 有状态的, 将状态保存在内存里, 用内置http服务器实现, 恐怕是代价最小的, 既然是监控程序, 最好占用资源比较小, 所以采用一体化的设计"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# [muduo/examples/procmon](https://github.com/chenshuo/muduo/tree/master/examples/procmon) *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## [procmon.cc](https://github.com/chenshuo/muduo/blob/master/examples/procmon/procmon.cc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// https://github.com/chenshuo/muduo/blob/master/examples/procmon/procmon.cc\n",
    "\n",
    "#include \"examples/procmon/plot.h\"\n",
    "\n",
    "#include \"muduo/base/FileUtil.h\"\n",
    "#include \"muduo/base/Logging.h\"\n",
    "#include \"muduo/base/ProcessInfo.h\"\n",
    "#include \"muduo/net/EventLoop.h\"\n",
    "#include \"muduo/net/http/HttpRequest.h\"\n",
    "#include \"muduo/net/http/HttpResponse.h\"\n",
    "#include \"muduo/net/http/HttpServer.h\"\n",
    "\n",
    "#include <boost/algorithm/string/replace.hpp>\n",
    "#include <boost/circular_buffer.hpp>\n",
    "\n",
    "#include <sstream>\n",
    "#include <type_traits>\n",
    "\n",
    "#include <dirent.h>\n",
    "#include <stdarg.h>\n",
    "#include <stdio.h>\n",
    "#include <sys/stat.h>\n",
    "#define __STDC_FORMAT_MACROS\n",
    "#include <inttypes.h>\n",
    "\n",
    "using namespace muduo;\n",
    "using namespace muduo::net;\n",
    "\n",
    "// TODO:\n",
    "// - what if process exits?\n",
    "//\n",
    "\n",
    "// Represent parsed /proc/pid/stat\n",
    "struct StatData\n",
    "{\n",
    "  void parse(const char* startAtState, int kbPerPage)\n",
    "  {\n",
    "    // istringstream is probably not the most efficient way to parse it,\n",
    "    // see muduo-protorpc/examples/collect/ProcFs.cc for alternatives.\n",
    "    std::istringstream iss(startAtState);\n",
    "\n",
    "    //            0    1    2    3     4    5       6   7 8 9  11  13   15\n",
    "    // 3770 (cat) R 3718 3770 3718 34818 3770 4202496 214 0 0 0 0 0 0 0 20\n",
    "    // 16  18     19      20 21                   22      23      24              25\n",
    "    //  0 1 0 298215 5750784 81 18446744073709551615 4194304 4242836 140736345340592\n",
    "    //              26\n",
    "    // 140736066274232 140575670169216 0 0 0 0 0 0 0 17 0 0 0 0 0 0\n",
    "\n",
    "    iss >> state;\n",
    "    iss >> ppid >> pgrp >> session >> tty_nr >> tpgid >> flags;\n",
    "    iss >> minflt >> cminflt >> majflt >> cmajflt;\n",
    "    iss >> utime >> stime >> cutime >> cstime;\n",
    "    iss >> priority >> nice >> num_threads >> itrealvalue >> starttime;\n",
    "    long vsize, rss;\n",
    "    iss >> vsize >> rss >> rsslim;\n",
    "    vsizeKb = vsize / 1024;\n",
    "    rssKb = rss * kbPerPage;\n",
    "  }\n",
    "  // int pid;\n",
    "  char state;\n",
    "  int ppid;\n",
    "  int pgrp;\n",
    "  int session;\n",
    "  int tty_nr;\n",
    "  int tpgid;\n",
    "  int flags;\n",
    "\n",
    "  long minflt;\n",
    "  long cminflt;\n",
    "  long majflt;\n",
    "  long cmajflt;\n",
    "\n",
    "  long utime;\n",
    "  long stime;\n",
    "  long cutime;\n",
    "  long cstime;\n",
    "\n",
    "  long priority;\n",
    "  long nice;\n",
    "  long num_threads;\n",
    "  long itrealvalue;\n",
    "  long starttime;\n",
    "\n",
    "  long vsizeKb;\n",
    "  long rssKb;\n",
    "  long rsslim;\n",
    "};\n",
    "\n",
    "static_assert(std::is_pod<StatData>::value, \"StatData should be POD.\");\n",
    "\n",
    "class Procmon : noncopyable\n",
    "{\n",
    " public:\n",
    "  Procmon(EventLoop* loop, pid_t pid, uint16_t port, const char* procname)\n",
    "    : kClockTicksPerSecond_(muduo::ProcessInfo::clockTicksPerSecond()),\n",
    "      kbPerPage_(muduo::ProcessInfo::pageSize() / 1024),\n",
    "      kBootTime_(getBootTime()),\n",
    "      pid_(pid),\n",
    "      server_(loop, InetAddress(port), getName()),\n",
    "      procname_(procname ? procname : ProcessInfo::procname(readProcFile(\"stat\")).as_string()),\n",
    "      hostname_(ProcessInfo::hostname()),\n",
    "      cmdline_(getCmdLine()),\n",
    "      ticks_(0),\n",
    "      cpu_usage_(600 / kPeriod_),  // 10 minutes\n",
    "      cpu_chart_(640, 100, 600, kPeriod_),\n",
    "      ram_chart_(640, 100, 7200, 30)\n",
    "  {\n",
    "    {\n",
    "    // chdir to the same cwd of the process being monitored.\n",
    "    string cwd = readLink(\"cwd\");\n",
    "    if (::chdir(cwd.c_str()))\n",
    "    {\n",
    "      LOG_SYSERR << \"Cannot chdir() to \" << cwd;\n",
    "    }\n",
    "    }\n",
    "\n",
    "    {\n",
    "    char cwd[1024];\n",
    "    if (::getcwd(cwd, sizeof cwd))\n",
    "    {\n",
    "      LOG_INFO << \"Current dir: \" << cwd;\n",
    "    }\n",
    "    }\n",
    "    memZero(&lastStatData_, sizeof lastStatData_);\n",
    "    server_.setHttpCallback(std::bind(&Procmon::onRequest, this, _1, _2));\n",
    "  }\n",
    "\n",
    "  void start()\n",
    "  {\n",
    "    tick();\n",
    "    server_.getLoop()->runEvery(kPeriod_, std::bind(&Procmon::tick, this)); // moduo eventloop\n",
    "    server_.start();\n",
    "  }\n",
    "\n",
    " private:\n",
    "\n",
    "  string getName() const\n",
    "  {\n",
    "    char name[256];\n",
    "    snprintf(name, sizeof name, \"procmon-%d\", pid_);\n",
    "    return name;\n",
    "  }\n",
    "\n",
    "  // 根据URL进行分发\n",
    "  // 这段代码没有体现网络编程, 既不关心TCP连接, 也没有读写socket fd, 这才是网络编程的常态\n",
    "  void onRequest(const HttpRequest& req, HttpResponse* resp) \n",
    "  {\n",
    "    resp->setStatusCode(HttpResponse::k200Ok);\n",
    "    resp->setStatusMessage(\"OK\");\n",
    "    resp->setContentType(\"text/plain\");\n",
    "    resp->addHeader(\"Server\", \"Muduo-Procmon\");\n",
    "\n",
    "    /*\n",
    "    if (!processExists(pid_))\n",
    "    {\n",
    "      resp->setStatusCode(HttpResponse::k404NotFound);\n",
    "      resp->setStatusMessage(\"Not Found\");\n",
    "      resp->setCloseConnection(true);\n",
    "      return;\n",
    "    }\n",
    "    */\n",
    "\n",
    "    if (req.path() == \"/\")\n",
    "    {\n",
    "      resp->setContentType(\"text/html\");\n",
    "      fillOverview(req.query()); // 主要内容\n",
    "      resp->setBody(response_.retrieveAllAsString());\n",
    "    }\n",
    "    else if (req.path() == \"/cmdline\")\n",
    "    {\n",
    "      resp->setBody(cmdline_);\n",
    "    }\n",
    "    else if (req.path() == \"/cpu.png\")\n",
    "    {\n",
    "      std::vector<double> cpu_usage;\n",
    "      for (size_t i = 0; i < cpu_usage_.size(); ++i)\n",
    "        cpu_usage.push_back(cpu_usage_[i].cpuUsage(kPeriod_, kClockTicksPerSecond_));\n",
    "      string png = cpu_chart_.plotCpu(cpu_usage); // 二进制图片的内容\n",
    "      resp->setContentType(\"image/png\");\n",
    "      resp->setBody(png);\n",
    "    }\n",
    "    // FIXME: replace with a map\n",
    "    else if (req.path() == \"/environ\")\n",
    "    {\n",
    "      resp->setBody(getEnviron());\n",
    "    }\n",
    "    else if (req.path() == \"/io\")\n",
    "    {\n",
    "      resp->setBody(readProcFile(\"io\"));\n",
    "    }\n",
    "    else if (req.path() == \"/limits\")\n",
    "    {\n",
    "      resp->setBody(readProcFile(\"limits\"));\n",
    "    }\n",
    "    else if (req.path() == \"/maps\")\n",
    "    {\n",
    "      resp->setBody(readProcFile(\"maps\"));\n",
    "    }\n",
    "    // numa_maps\n",
    "    else if (req.path() == \"/smaps\")\n",
    "    {\n",
    "      resp->setBody(readProcFile(\"smaps\"));\n",
    "    }\n",
    "    else if (req.path() == \"/status\")\n",
    "    {\n",
    "      resp->setBody(readProcFile(\"status\"));\n",
    "    }\n",
    "    else if (req.path() == \"/files\")\n",
    "    {\n",
    "      listFiles();\n",
    "      resp->setBody(response_.retrieveAllAsString());\n",
    "    }\n",
    "    else if (req.path() == \"/threads\")\n",
    "    {\n",
    "      listThreads();\n",
    "      resp->setBody(response_.retrieveAllAsString());\n",
    "    }\n",
    "    else\n",
    "    {\n",
    "      resp->setStatusCode(HttpResponse::k404NotFound);\n",
    "      resp->setStatusMessage(\"Not Found\");\n",
    "      resp->setCloseConnection(true);\n",
    "    }\n",
    "  }\n",
    "\n",
    "  // 收集数据, 接受请求, 变为html页面响应\n",
    "  void fillOverview(const string& query)\n",
    "  {\n",
    "    response_.retrieveAll();\n",
    "    Timestamp now = Timestamp::now();\n",
    "    appendResponse(\"<html><head><title>%s on %s</title>\\n\",\n",
    "                   procname_.c_str(), hostname_.c_str());\n",
    "    fillRefresh(query);\n",
    "    appendResponse(\"</head><body>\\n\");\n",
    "\n",
    "    string stat = readProcFile(\"stat\");\n",
    "    if (stat.empty())\n",
    "    {\n",
    "      appendResponse(\"<h1>PID %d doesn't exist.</h1></body></html>\", pid_);\n",
    "      return;\n",
    "    }\n",
    "    int pid = atoi(stat.c_str());\n",
    "    assert(pid == pid_);\n",
    "    // 除了CPU使用率, 其他都可以通过读取PROC获取. \n",
    "    // 只有CPU使用率需要一个背景线程, 实际需要一个IO线程周期性收集\n",
    "    StringPiece procname = ProcessInfo::procname(stat);\n",
    "    appendResponse(\"<h1>%s on %s</h1>\\n\",\n",
    "                   procname.as_string().c_str(), hostname_.c_str());\n",
    "    response_.append(\"<p>Refresh <a href=\\\"?refresh=1\\\">1s</a> \");\n",
    "    response_.append(\"<a href=\\\"?refresh=2\\\">2s</a> \");\n",
    "    response_.append(\"<a href=\\\"?refresh=5\\\">5s</a> \");\n",
    "    response_.append(\"<a href=\\\"?refresh=15\\\">15s</a> \");\n",
    "    response_.append(\"<a href=\\\"?refresh=60\\\">60s</a>\\n\");\n",
    "    response_.append(\"<p><a href=\\\"/cmdline\\\">Command line</a>\\n\");\n",
    "    response_.append(\"<a href=\\\"/environ\\\">Environment variables</a>\\n\");\n",
    "    response_.append(\"<a href=\\\"/threads\\\">Threads</a>\\n\");\n",
    "\n",
    "    appendResponse(\"<p>Page generated at %s (UTC)\", now.toFormattedString().c_str());\n",
    "\n",
    "    response_.append(\"<p><table>\");\n",
    "    StatData statData;  // how about use lastStatData_ ?\n",
    "    memZero(&statData, sizeof statData);\n",
    "    statData.parse(procname.end()+1, kbPerPage_);  // end is ')'\n",
    "\n",
    "    appendTableRow(\"PID\", pid);\n",
    "    Timestamp started(getStartTime(statData.starttime));  // FIXME: cache it;\n",
    "    appendTableRow(\"Started at\", started.toFormattedString(false /*showMicroseconds*/) + \" (UTC)\");\n",
    "    appendTableRowFloat(\"Uptime (s)\", timeDifference(now, started));  // FIXME: format as days+H:M:S\n",
    "    appendTableRow(\"Executable\", readLink(\"exe\"));\n",
    "    appendTableRow(\"Current dir\", readLink(\"cwd\"));\n",
    "\n",
    "    appendTableRow(\"State\", getState(statData.state));\n",
    "    appendTableRowFloat(\"User time (s)\", getSeconds(statData.utime));\n",
    "    appendTableRowFloat(\"System time (s)\", getSeconds(statData.stime));\n",
    "\n",
    "    appendTableRow(\"VmSize (KiB)\", statData.vsizeKb);\n",
    "    appendTableRow(\"VmRSS (KiB)\", statData.rssKb);\n",
    "    appendTableRow(\"Threads\", statData.num_threads);\n",
    "    // 嵌入到图片中; 或者使用base64编码\n",
    "    appendTableRow(\"CPU usage\", \"<img src=\\\"/cpu.png\\\" height=\\\"100\\\" witdh=\\\"640\\\">\");\n",
    "\n",
    "    appendTableRow(\"Priority\", statData.priority);\n",
    "    appendTableRow(\"Nice\", statData.nice);\n",
    "\n",
    "    appendTableRow(\"Minor page faults\", statData.minflt);\n",
    "    appendTableRow(\"Major page faults\", statData.majflt);\n",
    "    // TODO: user\n",
    "\n",
    "    response_.append(\"</table>\");\n",
    "    response_.append(\"</body></html>\");\n",
    "  }\n",
    "\n",
    "  void fillRefresh(const string& query)\n",
    "  {\n",
    "    size_t p = query.find(\"refresh=\");\n",
    "    if (p != string::npos)\n",
    "    {\n",
    "      int seconds = atoi(query.c_str()+p+8);\n",
    "      if (seconds > 0)\n",
    "      {\n",
    "        appendResponse(\"<meta http-equiv=\\\"refresh\\\" content=\\\"%d\\\">\\n\", seconds);\n",
    "      }\n",
    "    }\n",
    "  }\n",
    "\n",
    "  static int dirFilter(const struct dirent* d)\n",
    "  {\n",
    "    return (d->d_name[0] != '.');\n",
    "  }\n",
    "\n",
    "  static char getDirType(char d_type)\n",
    "  {\n",
    "    switch (d_type)\n",
    "    {\n",
    "      case DT_REG: return '-';\n",
    "      case DT_DIR: return 'd';\n",
    "      case DT_LNK: return 'l';\n",
    "      default: return '?';\n",
    "    }\n",
    "  }\n",
    "\n",
    "  void listFiles()\n",
    "  {\n",
    "    struct dirent** namelist = NULL;\n",
    "    int result = ::scandir(\".\", &namelist, dirFilter, alphasort);\n",
    "    for (int i = 0; i < result; ++i)\n",
    "    {\n",
    "      struct stat stat;\n",
    "      if (::lstat(namelist[i]->d_name, &stat) == 0)\n",
    "      {\n",
    "        Timestamp mtime(stat.st_mtime * Timestamp::kMicroSecondsPerSecond);\n",
    "        int64_t size = stat.st_size;\n",
    "        appendResponse(\"%c %9\" PRId64 \" %s %s\", getDirType(namelist[i]->d_type), size,\n",
    "                       mtime.toFormattedString(/*showMicroseconds=*/false).c_str(),\n",
    "                       namelist[i]->d_name);\n",
    "        if (namelist[i]->d_type == DT_LNK)\n",
    "        {\n",
    "          char link[1024];\n",
    "          ssize_t len = ::readlink(namelist[i]->d_name, link, sizeof link - 1);\n",
    "          if (len > 0)\n",
    "          {\n",
    "            link[len] = '\\0';\n",
    "            appendResponse(\" -> %s\", link);\n",
    "          }\n",
    "        }\n",
    "        appendResponse(\"\\n\");\n",
    "      }\n",
    "      ::free(namelist[i]);\n",
    "    }\n",
    "    ::free(namelist);\n",
    "  }\n",
    "\n",
    "  void listThreads()\n",
    "  {\n",
    "    response_.retrieveAll();\n",
    "    // FIXME: implement this\n",
    "  }\n",
    "\n",
    "  string readProcFile(const char* basename)\n",
    "  {\n",
    "    char filename[256];\n",
    "    snprintf(filename, sizeof filename, \"/proc/%d/%s\", pid_, basename);\n",
    "    string content;\n",
    "    FileUtil::readFile(filename, 1024*1024, &content);\n",
    "    return content;\n",
    "  }\n",
    "\n",
    "  string readLink(const char* basename)\n",
    "  {\n",
    "    char filename[256];\n",
    "    snprintf(filename, sizeof filename, \"/proc/%d/%s\", pid_, basename);\n",
    "    char link[1024];\n",
    "    ssize_t len = ::readlink(filename, link, sizeof link);\n",
    "    string result;\n",
    "    if (len > 0)\n",
    "    {\n",
    "      result.assign(link, len);\n",
    "    }\n",
    "    return result;\n",
    "  }\n",
    "\n",
    "  int appendResponse(const char* fmt, ...) __attribute__ ((format (printf, 2, 3)));\n",
    "\n",
    "  void appendTableRow(const char* name, long value)\n",
    "  {\n",
    "    appendResponse(\"<tr><td>%s</td><td>%ld</td></tr>\\n\", name, value);\n",
    "  }\n",
    "\n",
    "  void appendTableRowFloat(const char* name, double value)\n",
    "  {\n",
    "    appendResponse(\"<tr><td>%s</td><td>%.2f</td></tr>\\n\", name, value);\n",
    "  }\n",
    "\n",
    "  void appendTableRow(const char* name, StringArg value)\n",
    "  {\n",
    "    appendResponse(\"<tr><td>%s</td><td>%s</td></tr>\\n\", name, value.c_str());\n",
    "  }\n",
    "\n",
    "  string getCmdLine()\n",
    "  {\n",
    "    return boost::replace_all_copy(readProcFile(\"cmdline\"), string(1, '\\0'), \"\\n\\t\");\n",
    "  }\n",
    "\n",
    "  string getEnviron()\n",
    "  {\n",
    "    return boost::replace_all_copy(readProcFile(\"environ\"), string(1, '\\0'), \"\\n\");\n",
    "  }\n",
    "\n",
    "  Timestamp getStartTime(long starttime)\n",
    "  {\n",
    "    return Timestamp(Timestamp::kMicroSecondsPerSecond * kBootTime_\n",
    "                     + Timestamp::kMicroSecondsPerSecond * starttime / kClockTicksPerSecond_);\n",
    "  }\n",
    "\n",
    "  double getSeconds(long ticks)\n",
    "  {\n",
    "    return static_cast<double>(ticks) / kClockTicksPerSecond_;\n",
    "  }\n",
    "\n",
    "  void tick()\n",
    "  {\n",
    "    string stat = readProcFile(\"stat\");  // FIXME: catch file descriptor 读stat文件\n",
    "    if (stat.empty())\n",
    "      return;\n",
    "    StringPiece procname = ProcessInfo::procname(stat);\n",
    "    StatData statData;\n",
    "    memZero(&statData, sizeof statData);\n",
    "    statData.parse(procname.end()+1, kbPerPage_);  // end is ')'\n",
    "    if (ticks_ > 0) // 读到CPU时间\n",
    "    {\n",
    "      CpuTime time;\n",
    "      time.userTime_ = std::max(0, static_cast<int>(statData.utime - lastStatData_.utime));\n",
    "      time.sysTime_ = std::max(0, static_cast<int>(statData.stime - lastStatData_.stime));\n",
    "      cpu_usage_.push_back(time); // 放到环形数组\n",
    "    }\n",
    "\n",
    "    lastStatData_ = statData;\n",
    "    ++ticks_;\n",
    "  }\n",
    "\n",
    "  //\n",
    "  // static member functions\n",
    "  //\n",
    "\n",
    "  static const char* getState(char state)\n",
    "  {\n",
    "    // One character from the string \"RSDZTW\" where R is running, S is sleeping in\n",
    "    // an interruptible wait, D is waiting in uninterruptible disk sleep, Z is zombie,\n",
    "    // T is traced or stopped (on a signal), and W is paging.\n",
    "    switch (state)\n",
    "    {\n",
    "      case 'R':\n",
    "        return \"Running\";\n",
    "      case 'S':\n",
    "        return \"Sleeping\";\n",
    "      case 'D':\n",
    "        return \"Disk sleep\";\n",
    "      case 'Z':\n",
    "        return \"Zombie\";\n",
    "      default:\n",
    "        return \"Unknown\";\n",
    "    }\n",
    "  }\n",
    "\n",
    "  static long getLong(const string& status, const char* key)\n",
    "  {\n",
    "    long result = 0;\n",
    "    size_t pos = status.find(key);\n",
    "    if (pos != string::npos)\n",
    "    {\n",
    "      result = ::atol(status.c_str() + pos + strlen(key));\n",
    "    }\n",
    "    return result;\n",
    "  }\n",
    "\n",
    "  static long getBootTime()\n",
    "  {\n",
    "    string stat;\n",
    "    FileUtil::readFile(\"/proc/stat\", 65536, &stat);\n",
    "    return getLong(stat, \"btime \");\n",
    "  }\n",
    "\n",
    "  struct CpuTime\n",
    "  {\n",
    "    int userTime_;\n",
    "    int sysTime_;\n",
    "    double cpuUsage(double kPeriod, double kClockTicksPerSecond) const\n",
    "    {\n",
    "      return (userTime_ + sysTime_) / (kClockTicksPerSecond * kPeriod); // 目前画图将用户态时间和内核时间画在一个曲线上, 可以将其分开, 观察哪个计算时间多\n",
    "    }\n",
    "  };\n",
    "\n",
    "  const static int kPeriod_ = 2.0;\n",
    "  const int kClockTicksPerSecond_;\n",
    "  const int kbPerPage_;\n",
    "  const long kBootTime_;  // in Unix-time\n",
    "  const pid_t pid_;\n",
    "  HttpServer server_;\n",
    "  const string procname_;\n",
    "  const string hostname_;\n",
    "  const string cmdline_;\n",
    "  int ticks_;\n",
    "  StatData lastStatData_;\n",
    "  boost::circular_buffer<CpuTime> cpu_usage_;\n",
    "  Plot cpu_chart_;\n",
    "  Plot ram_chart_;\n",
    "  // scratch variables\n",
    "  Buffer response_;\n",
    "};\n",
    "\n",
    "// define outline for __attribute__\n",
    "int Procmon::appendResponse(const char* fmt, ...)\n",
    "{\n",
    "  char buf[1024];\n",
    "  va_list args;\n",
    "  va_start(args, fmt);\n",
    "  int ret = vsnprintf(buf, sizeof buf, fmt, args);\n",
    "  va_end(args);\n",
    "  response_.append(buf);\n",
    "  return ret;\n",
    "}\n",
    "\n",
    "bool processExists(pid_t pid)\n",
    "{\n",
    "  char filename[256];\n",
    "  snprintf(filename, sizeof filename, \"/proc/%d/stat\", pid);\n",
    "  return ::access(filename, R_OK) == 0;\n",
    "}\n",
    "\n",
    "int main(int argc, char* argv[])\n",
    "{\n",
    "  if (argc < 3)\n",
    "  {\n",
    "    printf(\"Usage: %s pid port [name]\\n\", argv[0]);\n",
    "    return 0;\n",
    "  }\n",
    "  int pid = atoi(argv[1]);\n",
    "  if (!processExists(pid))\n",
    "  {\n",
    "    printf(\"Process %d doesn't exist.\\n\", pid);\n",
    "    return 1;\n",
    "  }\n",
    "\n",
    "  EventLoop loop;\n",
    "  uint16_t port = static_cast<uint16_t>(atoi(argv[2]));\n",
    "  Procmon procmon(&loop, pid, port, argc > 3 ? argv[3] : NULL);\n",
    "  procmon.start();\n",
    "  loop.loop();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 主要有两个部分: \n",
    "  - 周期性的`class Procmo::tick()`, 收集CPU使用率\n",
    "  - `server_.setHttpCallback(std::bind(&Procmon::onRequest, this, _1, _2));`: 响应http请求"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## [plot.cc](https://github.com/chenshuo/muduo/blob/master/examples/procmon/plot.cc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// https://github.com/chenshuo/muduo/blob/master/examples/procmon/plot.cc\n",
    "\n",
    "#include \"examples/procmon/plot.h\"\n",
    "#include <algorithm>\n",
    "\n",
    "#include <math.h>\n",
    "\n",
    "#include <gd.h> // 使用gd画图\n",
    "#include <gdfonts.h>\n",
    "\n",
    "struct Plot::MyGdFont : public gdFont {};\n",
    "\n",
    "Plot::Plot(int width, int height, int totalSeconds, int samplingPeriod)\n",
    "  : width_(width),\n",
    "    height_(height),\n",
    "    totalSeconds_(totalSeconds),\n",
    "    samplingPeriod_(samplingPeriod),\n",
    "    image_(gdImageCreate(width_, height_)),\n",
    "    font_(static_cast<MyGdFont*>(gdFontGetSmall())),\n",
    "    fontWidth_(font_->w),\n",
    "    fontHeight_(font_->h),\n",
    "    background_(gdImageColorAllocate(image_, 255, 255, 240)),\n",
    "    black_(gdImageColorAllocate(image_, 0, 0, 0)),\n",
    "    gray_(gdImageColorAllocate(image_, 200, 200, 200)),\n",
    "    blue_(gdImageColorAllocate(image_, 128, 128, 255)),\n",
    "    kRightMargin_(3 * fontWidth_ + 5),\n",
    "    ratioX_(static_cast<double>(samplingPeriod_ * (width_ - kLeftMargin_ - kRightMargin_)) / totalSeconds_)\n",
    "{\n",
    "  // gdImageSetAntiAliased(image_, black_);\n",
    "}\n",
    "\n",
    "Plot::~Plot()\n",
    "{\n",
    "  gdImageDestroy(image_);\n",
    "}\n",
    "\n",
    "muduo::string Plot::plotCpu(const std::vector<double>& data)\n",
    "{\n",
    "  gdImageFilledRectangle(image_, 0, 0, width_, height_, background_);\n",
    "  if (data.size() > 1)\n",
    "  {\n",
    "    gdImageSetThickness(image_, 2);\n",
    "    double max = *std::max_element(data.begin(), data.end());\n",
    "    if (max >= 10.0)\n",
    "    {\n",
    "      max = ceil(max);\n",
    "    }\n",
    "    else\n",
    "    {\n",
    "      max = std::max(0.1, ceil(max*10.0) / 10.0);\n",
    "    }\n",
    "    label(max);\n",
    "\n",
    "    for (size_t i = 0; i < data.size()-1; ++i)\n",
    "    {\n",
    "      gdImageLine(image_,\n",
    "                  getX(i, data.size()),\n",
    "                  getY(data[i] / max),\n",
    "                  getX(i+1, data.size()),\n",
    "                  getY(data[i+1]/max),\n",
    "                  black_);\n",
    "    }\n",
    "  }\n",
    "\n",
    "  int total = totalSeconds_/samplingPeriod_;\n",
    "  gdImageSetThickness(image_, 1);\n",
    "  gdImageLine(image_, getX(0, total), getY(0)+2, getX(total, total), getY(0)+2, gray_);\n",
    "  gdImageLine(image_, getX(total, total), getY(0)+2, getX(total, total), getY(1)+2, gray_);\n",
    "  return toPng();\n",
    "}\n",
    "\n",
    "void Plot::label(double maxValue)\n",
    "{\n",
    "    char buf[64];\n",
    "    if (maxValue >= 10.0)\n",
    "      snprintf(buf, sizeof buf, \"%.0f\", maxValue);\n",
    "    else\n",
    "      snprintf(buf, sizeof buf, \"%.1f\", maxValue);\n",
    "\n",
    "    gdImageString(image_,\n",
    "                  font_,\n",
    "                  width_ - kRightMargin_ + 3,\n",
    "                  kMarginY_ - 3,\n",
    "                  reinterpret_cast<unsigned char*>(buf),\n",
    "                  black_);\n",
    "\n",
    "    snprintf(buf, sizeof buf, \"0\");\n",
    "    gdImageString(image_,\n",
    "                  font_,\n",
    "                  width_ - kRightMargin_ + 3,\n",
    "                  height_ - kMarginY_ - 3 - fontHeight_ / 2,\n",
    "                  reinterpret_cast<unsigned char*>(buf),\n",
    "                  gray_);\n",
    "\n",
    "    snprintf(buf, sizeof buf, \"-%ds\", totalSeconds_);\n",
    "    gdImageString(image_,\n",
    "                  font_,\n",
    "                  kLeftMargin_,\n",
    "                  height_ - kMarginY_ - fontHeight_,\n",
    "                  reinterpret_cast<unsigned char*>(buf),\n",
    "                  blue_);\n",
    "}\n",
    "\n",
    "int Plot::getX(ssize_t i, ssize_t total) const\n",
    "{\n",
    "  double x = (width_ - kLeftMargin_ - kRightMargin_) + static_cast<double>(i - total) * ratioX_;\n",
    "  return static_cast<int>(x + 0.5) + kLeftMargin_;\n",
    "}\n",
    "\n",
    "int Plot::getY(double value) const\n",
    "{\n",
    "  return static_cast<int>((1.0 - value) * (height_-2*kMarginY_) + 0.5) + kMarginY_;\n",
    "}\n",
    "\n",
    "muduo::string Plot::toPng()\n",
    "{\n",
    "  int size = 0;\n",
    "  void* png = gdImagePngPtr(image_, &size);\n",
    "  muduo::string result(static_cast<char*>(png), size);\n",
    "  gdFree(png);\n",
    "  return result;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dummy CPU load "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 控制CPU使用率的思路: 脉冲宽度调制或者说控制占空比(忙和闲的比例). 没有一个直接的方法设定CPU使用率是多高, 间接的方法, 0~1s, 分为100份, 20份繁忙, 但是最好均匀分布, 20ms忙, 80ms闲, 这样1s内20%忙.\n",
    "- 如何测试CPU使用率: 只能测一段时间内CPU忙的比例, 时间点的比例没有意义. 比如, 过去1ms(1000ms)内, CPU繁忙了300ms, 使用率就是30%, 多个核可能会大于100%, 1500ms繁忙, 使用率即150%"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Want to see a sine wave in CPU usage graph?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### [dummyload.cc](https://github.com/chenshuo/muduo/blob/master/examples/procmon/dummyload.cc) *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// https://github.com/chenshuo/muduo/blob/master/examples/procmon/dummyload.cc\n",
    "\n",
    "#include \"muduo/base/Atomic.h\"\n",
    "#include \"muduo/base/Condition.h\"\n",
    "#include \"muduo/base/CurrentThread.h\"\n",
    "#include \"muduo/base/Mutex.h\"\n",
    "#include \"muduo/base/Thread.h\"\n",
    "#include \"muduo/base/Timestamp.h\"\n",
    "#include \"muduo/net/EventLoop.h\"\n",
    "\n",
    "#include <math.h>\n",
    "#include <stdio.h>\n",
    "\n",
    "using namespace muduo;\n",
    "using namespace muduo::net;\n",
    "\n",
    "int g_cycles = 0;\n",
    "int g_percent = 82;\n",
    "AtomicInt32 g_done;\n",
    "bool g_busy = false;\n",
    "MutexLock g_mutex;\n",
    "Condition g_cond(g_mutex);\n",
    "\n",
    "double busy(int cycles)\n",
    "{\n",
    "  double result = 0;\n",
    "  for (int i = 0; i < cycles; ++i)\n",
    "  {\n",
    "    result += sqrt(i) * sqrt(i+1); // 不访问内存, 不占内存带宽, 在CPU内计算\n",
    "  }\n",
    "  return result;\n",
    "}\n",
    "\n",
    "double getSeconds(int cycles)\n",
    "{\n",
    "  Timestamp start = Timestamp::now();\n",
    "  busy(cycles);\n",
    "  return timeDifference(Timestamp::now(), start);\n",
    "}\n",
    "\n",
    "void findCycles()\n",
    "{\n",
    "  g_cycles = 1000;\n",
    "  while (getSeconds(g_cycles) < 0.001)\n",
    "    g_cycles = g_cycles + g_cycles / 4;  // * 1.25\n",
    "  printf(\"cycles %d\\n\", g_cycles);\n",
    "}\n",
    "\n",
    "void threadFunc()\n",
    "{\n",
    "  while (g_done.get() == 0)\n",
    "  {\n",
    "    {\n",
    "    MutexLockGuard guard(g_mutex);\n",
    "    while (!g_busy)\n",
    "      g_cond.wait();\n",
    "    }\n",
    "    busy(g_cycles);\n",
    "  }\n",
    "  printf(\"thread exit\\n\");\n",
    "}\n",
    "\n",
    "// this is open-loop control 开环控制/闭环控制\n",
    "void load(int percent)\n",
    "{\n",
    "  percent = std::max(0, percent);\n",
    "  percent = std::min(100, percent);\n",
    "\n",
    "  // Bresenham's line algorithm\n",
    "  int err = 2*percent - 100;\n",
    "  int count = 0;\n",
    "\n",
    "  for (int i = 0; i < 100; ++i)\n",
    "  {\n",
    "    bool busy = false;\n",
    "    if (err > 0)\n",
    "    {\n",
    "      busy = true;\n",
    "      err += 2*(percent - 100);\n",
    "      ++count;\n",
    "      // printf(\"%2d, \", i);\n",
    "    }\n",
    "    else\n",
    "    {\n",
    "      err += 2*percent;\n",
    "    }\n",
    "\n",
    "    {\n",
    "    MutexLockGuard guard(g_mutex);\n",
    "    g_busy = busy;\n",
    "    g_cond.notifyAll();\n",
    "    }\n",
    "\n",
    "    CurrentThread::sleepUsec(10*1000); // 10 ms\n",
    "  }\n",
    "  assert(count == percent);\n",
    "}\n",
    "\n",
    "void fixed()\n",
    "{\n",
    "  while (true)\n",
    "  {\n",
    "    load(g_percent); // 运行1s\n",
    "  }\n",
    "}\n",
    "\n",
    "void cosine()\n",
    "{\n",
    "  while (true)\n",
    "    for (int i = 0; i < 200; ++i) // 200s一个周期\n",
    "    {\n",
    "      int percent = static_cast<int>((1.0 + cos(i * 3.14159 / 100)) / 2 * g_percent + 0.5); // g_percent峰值\n",
    "      load(percent);\n",
    "    }\n",
    "}\n",
    "\n",
    "void sawtooth()\n",
    "{\n",
    "  while (true)\n",
    "    for (int i = 0; i <= 100; ++i)\n",
    "    {\n",
    "      int percent = static_cast<int>(i / 100.0 * g_percent);\n",
    "      load(percent);\n",
    "    }\n",
    "}\n",
    "\n",
    "// 主线程一直闲, 控制其他线程\n",
    "int main(int argc, char* argv[])\n",
    "{\n",
    "  if (argc < 2)\n",
    "  {\n",
    "    printf(\"Usage: %s [fctsz] [percent] [num_threads]\\n\", argv[0]);\n",
    "    return 0;\n",
    "  }\n",
    "\n",
    "  printf(\"pid %d\\n\", getpid()); // 打印pid, 要用procmon查看\n",
    "  findCycles();\n",
    "\n",
    "  g_percent = argc > 2 ? atoi(argv[2]) : 43;\n",
    "  int numThreads = argc > 3 ? atoi(argv[3]) : 1;\n",
    "  std::vector<std::unique_ptr<Thread>> threads;\n",
    "  for (int i = 0; i < numThreads; ++i)\n",
    "  {\n",
    "    threads.emplace_back(new Thread(threadFunc));\n",
    "    threads.back()->start();\n",
    "  }\n",
    "\n",
    "  switch (argv[1][0])\n",
    "  {\n",
    "    case 'f': // CPU曲线类型: 固定\n",
    "    {\n",
    "      fixed();\n",
    "    }\n",
    "    break;\n",
    "\n",
    "    case 'c': // CPU曲线类型: COS\n",
    "    {\n",
    "      cosine();\n",
    "    }\n",
    "    break;\n",
    "\n",
    "    case 'z': // CPU曲线类型: 锯齿\n",
    "    {\n",
    "      sawtooth();\n",
    "    }\n",
    "    break;\n",
    "\n",
    "    // TODO: square and triangle waves\n",
    "\n",
    "    default:\n",
    "    break;\n",
    "  }\n",
    "\n",
    "  g_done.getAndSet(1);\n",
    "  {\n",
    "  MutexLockGuard guard(g_mutex);\n",
    "  g_busy = true;\n",
    "  g_cond.notifyAll();\n",
    "  }\n",
    "  for (int i = 0; i < numThreads; ++i)\n",
    "  {\n",
    "    threads[i]->join();\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# procmon性能测试"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- procmon 查看init进程\n",
    "\n",
    "```shell\n",
    "procmon 1 3000 # 假设该进程pid是3671\n",
    "```\n",
    "\n",
    "- procmon 查看3671进程\n",
    "\n",
    "```shell\n",
    "procmon 3671 2345\n",
    "```\n",
    "\n",
    "- 打开浏览器 `10.0.0.37:2345`\n",
    "\n",
    "- 使用长连接\n",
    "```shell\n",
    "ab -k -n 100000 http://localhost:3000/\n",
    "```\n",
    "\n",
    "```shell\n",
    "ab -k -n 1000000 http://localhost:3000/\n",
    "```\n",
    "\n",
    "```shell\n",
    "utop\n",
    "```\n",
    "\n",
    "```shell\n",
    "ab -k -n 500000 -c 5 http://localhost:3000/\n",
    "```\n",
    "\n",
    "- 使用短连接\n",
    "```shell\n",
    "ab  -n 100000 -c 5 http://localhost:3000/\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Benchmarking results on Celeron 1037U"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`/`: IO intensive\n",
    "- Short connections: 5000 requests per second\n",
    "- Keep-Alive: 12,780 RPS (<100us)\n",
    "- Keep-Alive, 2 connections: 17,668 RPS"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`/cpu.png`: CPU intensive, `plot_test.cc`\n",
    "\n",
    "- Short connections: 571 requests per second\n",
    "- Keep-Alive: 670 RPS (1.5ms)\n",
    "- Keep-Alive, 2 connections: 720 RPS"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 生成PNG图片耗时\n",
    "- CPU密集型: 使用多线程提高; 但是对于procmon要限制CPU占用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Extensions and improvements"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Reduce overhead by caching file descriptor to `/proc/pid/stat`\n",
    "- 不用每次都是open close文件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Show CPU usage per thread\n",
    "- 显示每个线程的使用率, 还有线程名字"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Show memoryusage over time\n",
    "- 增加内存率的图标, 例如过去2小时"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Collect machine wide data\n",
    "  - [muduo-protorpc/examples/collect](https://github.com/chenshuo/muduo-protorpc/tree/cpp11/examples/collect)\n",
    "\n",
    "- 监控整个机器的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Monitoring processes running on multiple machines\n",
    "  - A simple HTML with IFRAME could work\n",
    "- 聚合数据"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# A project idea: nohup on web"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- A customized HTTP server, fork and run a command line tool, turns its `STDOUT/STDERR` into web pages.\n",
    "  - Watching 'apt-get upgrade' or 'make boost' progress in real time\n",
    "- web版本的nohup; 方便共享"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Security concerns"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# What is network programming"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Level 1: doesn’t even know MAC, eg. hubs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Level 2: knows MAC but not IP,eg. switches"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Level 3: knows IP but not port, eg. routers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Level 4: knows IP and port, eg. NAT\n",
    "- 前4层与网络设备打交道, 不关注数据本身"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Level 7: cares about payload, eg. http proxy\n",
    "  - Parse, understand, use, consume and/or produce payload\n",
    "- 不跟网络打交道, 只关注收发数据本身的内容"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Focus on business logic"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Network programming is not about fiddling sockets API"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Instead, fit your business logic into the framework and let network libraryto worry about details"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 程序开发目的: 将业务(领域知识)逻辑添加到框架里, 业务逻辑如何配合网络库; 底层的库去关心数据的收发"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "name": "C++17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
